﻿using System;
using System.Security.Cryptography;
using System.IO;
using System.Collections;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Text;

/// <summary>
/// This class is designed by bongvd for the Encode password , files
///
namespace SERP.Commons
{
    public class Security
    {
        public static string RemoveQuoteString(string strvalidate)
        {
            return strvalidate.Replace("'", "''");
        }
        public static string GenRandomPasword(int len)
        {
            string str = "";
            string strresult = "";
            for (int i = 0; i < 26; i++)
            {
                str += ((char)(65 + i)).ToString();
            }
            for (int i = 0; i < 26; i++)
            {
                str += ((char)(97 + i)).ToString();
            }
            for (int i = 0; i < 10; i++)
            {
                str += i.ToString();
            }
            Random rd = new Random();
            while (len > 0)
            {
                int i = rd.Next(str.Length);
                strresult += str[i].ToString();
                len--;
                System.Threading.Thread.Sleep(1);
            }
            return strresult;
        }
        //this function Encode Password by md5 
        //password encoded must be the same with server encode pass
        public static string EncriptPassWord(string password)
        {
            if (password.Trim() == "") return "";
            byte[] buff = System.Text.ASCIIEncoding.ASCII.GetBytes(password);
            System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();
            buff = md5.ComputeHash(buff);
            string strResult = "";
            for (int i = 0; i < buff.Length; i++)
            {
                strResult += string.Format("{0:x2}", buff[i]);
            }
            return strResult;
        }

        /// <summary>
        /// Tag to make sure this file is readable/decryptable by this class
        /// </summary>
        private const ulong FC_TAG = 0xFC010203040506CF;

        /// <summary>
        /// The amount of bytes to read from the file
        /// </summary>
        private const int BUFFER_SIZE = 128 * 1024;

        /// <summary>
        /// Checks to see if two byte array are equal
        /// </summary>
        /// <param name="b1">the first byte array</param>
        /// <param name="b2">the second byte array</param>
        /// <returns>true if b1.Length == b2.Length and each byte in b1 is
        /// equal to the corresponding byte in b2</returns>
        private static bool CheckByteArrays(byte[] b1, byte[] b2)
        {
            if (b1.Length == b2.Length)
            {
                for (int i = 0; i < b1.Length; ++i)
                {
                    if (b1[i] != b2[i])
                        return false;
                }
                return true;
            }
            return false;
        }

        /// <summary>
        /// Creates a Rijndael SymmetricAlgorithm for use in EncryptFile and DecryptFile
        /// </summary>
        /// <param name="password">the string to use as the password</param>
        /// <param name="salt">the salt to use with the password</param>
        /// <returns>A SymmetricAlgorithm for encrypting/decrypting with Rijndael</returns>
        private static SymmetricAlgorithm CreateRijndael(string password, byte[] salt)
        {
            PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, salt, "SHA256", 1000);
            SymmetricAlgorithm sma = Rijndael.Create();
            sma.KeySize = 256;
            sma.Key = pdb.GetBytes(32);
            sma.Padding = PaddingMode.PKCS7;
            return sma;
        }

        /// <summary>
        /// Crypto Random number generator for use in EncryptFile
        /// </summary>
        private static RandomNumberGenerator rand = new RNGCryptoServiceProvider();

        /// <summary>
        /// Generates a specified amount of random bytes
        /// </summary>
        /// <param name="count">the number of bytes to return</param>
        /// <returns>a byte array of count size filled with random bytes</returns>
        private static byte[] GenerateRandomBytes(int count)
        {
            byte[] bytes = new byte[count];
            rand.GetBytes(bytes);
            return bytes;
        }
        /// <summary>
        /// This takes an input file and encrypts it into the output file
        /// </summary>
        /// <param name="inFile">the file to encrypt</param>
        /// <param name="outFile">the file to write the encrypted data to</param>
        /// <param name="password">the password for use as the key</param>
        /// <param name="callback">the method to call to notify of progress</param>
        public static void EncryptFile(string inFile, string outFile, string password)
        {
            if (System.IO.File.Exists(outFile))
            {
                System.IO.File.Delete(outFile);
            }
            using (FileStream fin = File.OpenRead(inFile),
                        fout = File.OpenWrite(outFile))
            {
                long lSize = fin.Length; // the size of the input file for storing
                int size = (int)lSize;  // the size of the input file for progress
                byte[] bytes = new byte[BUFFER_SIZE]; // the buffer
                int read = -1; // the amount of bytes read from the input file
                int value = 0; // the amount overall read from the input file for progress

                // generate IV and Salt
                byte[] IV = GenerateRandomBytes(16);
                byte[] salt = GenerateRandomBytes(16);

                // create the crypting object
                SymmetricAlgorithm sma = CreateRijndael(password, salt);
                sma.IV = IV;

                // write the IV and salt to the beginning of the file
                fout.Write(IV, 0, IV.Length);
                fout.Write(salt, 0, salt.Length);

                // create the hashing and crypto streams
                HashAlgorithm hasher = SHA256.Create();
                using (CryptoStream cout = new CryptoStream(fout, sma.CreateEncryptor(), CryptoStreamMode.Write),
                            chash = new CryptoStream(Stream.Null, hasher, CryptoStreamMode.Write))
                {
                    // write the size of the file to the output file
                    BinaryWriter bw = new BinaryWriter(cout);
                    bw.Write(lSize);

                    // write the file cryptor tag to the file
                    bw.Write(FC_TAG);

                    // read and the write the bytes to the crypto stream in BUFFER_SIZEd chunks
                    while ((read = fin.Read(bytes, 0, bytes.Length)) != 0)
                    {
                        cout.Write(bytes, 0, read);
                        chash.Write(bytes, 0, read);
                        value += read;
                        //callback(0,size,value);
                    }
                    // flush and close the hashing object
                    chash.Flush();
                    chash.Close();

                    // read the hash
                    byte[] hash = hasher.Hash;

                    // write the hash to the end of the file
                    cout.Write(hash, 0, hash.Length);

                    // flush and close the cryptostream
                    cout.Flush();
                    cout.Close();
                }
            }
        }

        /// <summary>
        /// takes an input file and decrypts it to the output file
        /// </summary>
        /// <param name="inFile">the file to decrypt</param>
        /// <param name="outFile">the to write the decrypted data to</param>
        /// <param name="password">the password used as the key</param>
        /// <param name="callback">the method to call to notify of progress</param>
        public static void DecryptFile(string inFile, string outFile, string password)
        {
            // NOTE:  The encrypting algo was so much easier...
            // create and open the file streams
            if (System.IO.File.Exists(outFile))
            {
                System.IO.File.Delete(outFile);
            }
            using (FileStream fin = File.OpenRead(inFile),
                        fout = File.OpenWrite(outFile))
            {
                int size = (int)fin.Length; // the size of the file for progress notification
                byte[] bytes = new byte[BUFFER_SIZE]; // byte buffer
                int read = -1; // the amount of bytes read from the stream
                int value = 0;
                int outValue = 0; // the amount of bytes written out

                // read off the IV and Salt
                byte[] IV = new byte[16];
                fin.Read(IV, 0, 16);
                byte[] salt = new byte[16];
                fin.Read(salt, 0, 16);

                // create the crypting stream
                SymmetricAlgorithm sma = CreateRijndael(password, salt);
                sma.IV = IV;

                value = 32; // the value for the progress
                long lSize = -1; // the size stored in the input stream

                // create the hashing object, so that we can verify the file
                HashAlgorithm hasher = SHA256.Create();

                // create the cryptostreams that will process the file
                using (CryptoStream cin = new CryptoStream(fin, sma.CreateDecryptor(), CryptoStreamMode.Read),
                            chash = new CryptoStream(Stream.Null, hasher, CryptoStreamMode.Write))
                {
                    // read size from file
                    BinaryReader br = new BinaryReader(cin);
                    lSize = br.ReadInt64();
                    ulong tag = br.ReadUInt64();

                    if (FC_TAG != tag)
                        throw new CryptoHelpException("File Corrupted!");

                    //determine number of reads to process on the file
                    long numReads = lSize / BUFFER_SIZE;

                    // determine what is left of the file, after numReads
                    long slack = (long)lSize % BUFFER_SIZE;

                    // read the buffer_sized chunks
                    for (int i = 0; i < numReads; ++i)
                    {
                        read = cin.Read(bytes, 0, bytes.Length);
                        fout.Write(bytes, 0, read);
                        chash.Write(bytes, 0, read);
                        value += read;
                        outValue += read;
                        //callback(0,size,value);
                    }
                    // now read the slack
                    if (slack > 0)
                    {
                        read = cin.Read(bytes, 0, (int)slack);
                        fout.Write(bytes, 0, read);
                        chash.Write(bytes, 0, read);
                        value += read;
                        outValue += read;
                        //callback(0,size,value);
                    }
                    // flush and close the hashing stream
                    chash.Flush();
                    chash.Close();

                    // flush and close the output file
                    fout.Flush();
                    fout.Close();

                    // read the current hash value
                    byte[] curHash = hasher.Hash;

                    // get and compare the current and old hash values
                    byte[] oldHash = new byte[hasher.HashSize / 8];
                    read = cin.Read(oldHash, 0, oldHash.Length);
                    if ((oldHash.Length != read) || (!CheckByteArrays(oldHash, curHash)))
                        throw new CryptoHelpException("File Corrupted!");
                }

                // make sure the written and stored size are equal
                if (outValue != lSize)
                    throw new CryptoHelpException("File Sizes don't match!");
            }
        }


        private static Byte[] MaskPasword(string password)
        {
            Byte[] buff = new Byte[password.Length];
            for (int i = 0; i < password.Length; i++)
            {
                if (Char.IsDigit(password[i]))
                {
                    buff[i] = (Byte)((int)password[i] - 0x30);
                }
                else
                {
                    buff[i] = (Byte)((int)password[i] % 10);
                }
            }
            for (int i = 0; i < password.Length; i++)
            {
                if (buff[i] <= 0)
                    buff[i] = 1;
            }
            return buff;
        }
        
        public static string CreateLengthFixString(int iRoot, byte bLength)
        {
            return (iRoot + 1000000000).ToString().Substring(10 - bLength);
        }

        // Ham lay ngau nhien cac vi tri trong mot mang co n phan tu
        public static ArrayList RandomArray(ArrayList arrSource)
        {
            int n = arrSource.Count;
            ArrayList arrReturn = new ArrayList();
            Random rd = new Random(DateTime.Now.Millisecond);
            while (arrReturn.Count < n)
            {
                int i = rd.Next(n);
                if (!arrReturn.Contains(arrSource[i].ToString()))
                {
                    arrReturn.Add(arrSource[i].ToString());
                }
            }
            return arrReturn;
        }


        public static ArrayList RandomArray(int n)
        {
            ArrayList arrReturn = new ArrayList();
            Random rd = new Random(DateTime.Now.Millisecond);
            while (arrReturn.Count < n)
            {
                int i = rd.Next(n);
                if (!arrReturn.Contains(i.ToString()))
                {
                    arrReturn.Add(i.ToString());
                }
            }
            return arrReturn;
        }

        public static string EncryptString(string strIn,string password)
        {
            StringEncript stringEncript=new StringEncript(EncryptionAlgorithm.Rijndael,password);
            string outText= stringEncript.Encrypt(strIn);
            string tmp= Convert.ToBase64String(stringEncript.IV);
            outText+="$IV="+tmp;
            return outText;
        }

        public static string DecryptString(string encript, string password)
        {
            StringDecript stringEncript;
            int index = encript.LastIndexOf("$IV=");
            if(index>0)
            {
                stringEncript = new StringDecript(EncryptionAlgorithm.Rijndael,Convert.FromBase64String(encript.Substring(index+4)));
                encript = encript.Substring(0, index);
            }
            else
            {
                stringEncript = new StringDecript(EncryptionAlgorithm.Rijndael);
            }
            return stringEncript.DecryptString(encript,password);
        }











        public class DecryptTransformer
        {
            EncryptionAlgorithm algorithmID;
            string SecurityKey = "";
            Byte[] IV;
            bool bHasIV = false;
            public DecryptTransformer(EncryptionAlgorithm algID)
            {
                algorithmID = algID;
            }
            public DecryptTransformer(EncryptionAlgorithm algID, byte[] iv)
            {
                algorithmID = algID;
                IV = iv;
                bHasIV = true;
            }

            public EncryptionAlgorithm EncryptionAlgorithm
            {
                get
                {
                    return algorithmID;
                }
                set
                {
                    algorithmID = value;
                }
            }

            public void SetSecurityKey(string Key)
            {
                SecurityKey = Key;
            }
            public ICryptoTransform GetCryptoTransform()
            {
                bool bHasSecuityKey = false;
                if (SecurityKey.Length != 0)
                    bHasSecuityKey = true;

                byte[] key = Encoding.ASCII.GetBytes(SecurityKey);
                switch (algorithmID)
                {
                    case EncryptionAlgorithm.DES:
                        DES des = new DESCryptoServiceProvider();
                        if (bHasSecuityKey) des.Key = key;
                        if (bHasIV) des.IV = IV;
                        return des.CreateDecryptor();

                    case EncryptionAlgorithm.Rc2:
                        RC2 rc = new RC2CryptoServiceProvider();
                        if (bHasSecuityKey) rc.Key = key;
                        if (bHasIV) rc.IV = IV;
                        return rc.CreateDecryptor();
                    case EncryptionAlgorithm.Rijndael:
                        Rijndael rj = new RijndaelManaged();
                        if (bHasSecuityKey)
                        {
                            byte[] tmpkey = key;
                            if (key.Length != 32)
                            {
                                tmpkey = new byte[32];
                                if (key.Length < 32)
                                {
                                    Array.Copy(key, 0, tmpkey, 0, key.Length);
                                }
                                else
                                {
                                    Array.Copy(key, 0, tmpkey, 0, tmpkey.Length);
                                }
                            }
                            rj.Key = tmpkey;
                        }

                        if (bHasIV) rj.IV = IV; ;
                        return rj.CreateDecryptor();
                    case EncryptionAlgorithm.TripleDes:
                        TripleDES tDes = new TripleDESCryptoServiceProvider();
                        if (bHasSecuityKey) tDes.Key = key;
                        if (bHasIV) tDes.IV = IV;
                        return tDes.CreateDecryptor();
                    default:
                        throw new CryptographicException("Algorithm ID '" + algorithmID + "' not supported.");
                }
            }

        }

    }
    /// <summary>
    /// This is the exception class that is thrown throughout the Decryption process
    /// </summary>
    public class CryptoHelpException : ApplicationException
    {
        public CryptoHelpException(string msg) : base(msg) { }
    }

    public class StringDecript
    {
        EncryptionAlgorithm AlgoritmID;
        byte[] IV;
        public StringDecript(EncryptionAlgorithm algID)
        {
            AlgoritmID = algID;
        }
        public StringDecript(EncryptionAlgorithm algID, byte[] iv)
        {
            AlgoritmID = algID;
            IV = iv;
        }

        public Security.DecryptTransformer DecryptTransformer
        {
            get
            {
                throw new System.NotImplementedException();
            }
            set
            {
            }
        }

        public string DecryptString(string MainString, string key)
        {
            Security.DecryptTransformer dt = new Security.DecryptTransformer(AlgoritmID, IV);
            dt.SetSecurityKey(key);

            byte[] buffer = Convert.FromBase64String(MainString.Trim());
            MemoryStream ms = new MemoryStream(buffer);

            // Create a CryptoStream using the memory stream and the 
            // CSP DES key. 
            CryptoStream encStream = new CryptoStream(ms, dt.GetCryptoTransform(), CryptoStreamMode.Read);

            // Create a StreamReader for reading the stream.
            StreamReader sr = new StreamReader(encStream);

            // Read the stream as a string.
            string val = sr.ReadToEnd();

            // Close the streams.
            sr.Close();
            encStream.Close();
            ms.Close();

            return val;


        }
    }
    
    public class StringEncript
    {
        EncryptEngine engin;
        public byte[] IV;
        public StringEncript(EncryptionAlgorithm algID, string key)
        {
            engin = new EncryptEngine(algID, key);
        }

        public EncryptEngine EncryptEngine
        {
            get
            {
                return engin;
            }
            set
            {
                engin = value;
            }
        }
    
        public string Encrypt(string MainString)
        {
            MemoryStream memory = new MemoryStream();
            CryptoStream stream = new CryptoStream(memory, engin.GetCryptTransform(), CryptoStreamMode.Write);
            StreamWriter streamwriter = new StreamWriter(stream);
            streamwriter.WriteLine(MainString);
            streamwriter.Close();
            stream.Close();
            IV = engin.Vector;
            byte[] buffer = memory.ToArray();
            memory.Close();
            return Convert.ToBase64String(buffer);

        }
    }


    public enum EncryptionAlgorithm
    {
         DES = 0, Rc2, Rijndael, TripleDes 
    }
    public class EncryptEngine
    {
        bool bWithKey = false;
        EncryptionAlgorithm AlgoritmID;
        string keyword = "";
        public byte[] Vector;
        public EncryptEngine(EncryptionAlgorithm AlgID, string Key)
        {
            if (Key.Length == 0)
                bWithKey = false;
            else
                bWithKey = true;

            keyword = Key;
            AlgoritmID = AlgID;
        }

        public EncryptionAlgorithm EncryptionAlgorithm
        {
            get
            {
                return AlgoritmID;
            }
            set
            {
                AlgoritmID = value;
            }
        }
    
        public ICryptoTransform GetCryptTransform()
        {
            byte[] key = Encoding.ASCII.GetBytes(keyword);

            switch (AlgoritmID)
            {
                case EncryptionAlgorithm.DES:
                    DES des = new DESCryptoServiceProvider();
                    des.Mode = CipherMode.CBC;
                    if (bWithKey) des.Key = key;
                    Vector = des.IV;
                    return des.CreateEncryptor();
                case EncryptionAlgorithm.Rc2:
                    RC2 rc =new RC2CryptoServiceProvider();
                    rc.Mode = CipherMode.CBC;
                    if (bWithKey) rc.Key = key;
                    Vector = rc.IV;
                    return rc.CreateEncryptor();
                case EncryptionAlgorithm.Rijndael:
                    Rijndael rj = new RijndaelManaged();
                    rj.Mode = CipherMode.CBC;
                    if (bWithKey)
                    {
                        byte[] tmpkey = key;
                        if(key.Length!=32)
                        {
                            tmpkey = new byte[32];
                            if(key.Length<32)
                            {
                                Array.Copy(key,0,tmpkey,0,key.Length);
                            }
                            else
                            {
                                Array.Copy(key, 0, tmpkey, 0, tmpkey.Length);
                            }
                        }
                        rj.Key = tmpkey;
                    }
                    Vector = rj.IV;
                    return rj.CreateEncryptor();
                case EncryptionAlgorithm.TripleDes:
                    TripleDES tDes = new TripleDESCryptoServiceProvider();
                    tDes.Mode = CipherMode.CBC;
                    if (bWithKey) tDes.Key = key;
                    Vector = tDes.IV;
                    return tDes.CreateEncryptor();
                default:
                    throw new CryptographicException("Algorithm " + AlgoritmID + " Not Supported!");
            }
        }
        public static bool ValidateKeySize(EncryptionAlgorithm algID,int Lenght)
        {
            switch (algID)
            {
                case EncryptionAlgorithm.DES:
                    DES des = new DESCryptoServiceProvider();
                    return des.ValidKeySize(Lenght);
                case EncryptionAlgorithm.Rc2:
                    RC2 rc = new RC2CryptoServiceProvider();
                    return rc.ValidKeySize(Lenght);
                case EncryptionAlgorithm.Rijndael:
                    Rijndael rj = new RijndaelManaged();
                    return rj.ValidKeySize(Lenght);
                case EncryptionAlgorithm.TripleDes:
                    TripleDES tDes = new TripleDESCryptoServiceProvider();
                    return tDes.ValidKeySize(Lenght);
                default:
                    throw new CryptographicException("Algorithm " + algID + " Not Supported!");
            }
        }
    }
}